package com.sso.oauth.filter;

import com.alibaba.fastjson.JSON;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.sso.oauth.constants.AuthParameterNames;
import com.sso.oauth.utils.SessionHelper;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by Derry Luo on 2019/5/9.
 */
@Log4j
@Component
public class AccessInterceptor implements HandlerInterceptor {

    @Autowired
    private SessionHelper sessionHelper;

    @Override
    public boolean preHandle(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Object o) throws Exception {

        log.info("【Show Cookies】 =》 " + JSON.toJSONString(httpRequest.getCookies()));

        try {
            String currentUri = httpRequest.getRequestURL().toString();
            String queryStr = httpRequest.getQueryString();
            String fullUrl = currentUri + (queryStr != null ? "?" + queryStr : "");
            currentUri = currentUri.replaceAll("http:", "https:");

            // 校验是否已登录
            if (sessionHelper.isAuthenticated(httpRequest)) {
                // 已登录，判断是否需要刷新accessToken
                if (sessionHelper.isAuthDataExpired(httpRequest)) {
                    sessionHelper.updateAuthDataUsingRefreshToken(httpRequest);
                }
                return true;
            }
            // 未登录，判断请求信息中是否包含令牌：accessToken
            if (sessionHelper.containsAccessToken(httpRequest)) {
                // TODO 通过token去Graph换取用户信息，然后登录
                sessionHelper.processUserLogin(httpRequest, httpRequest.getParameter(AuthParameterNames.ACCESS_TOKEN));
                return true;
            }
            // 未登录，判断request中是否有授权信息/错误信息等，这里比较关键，一般从授权中心跳转回来之后，就走的这里的流程
            if (sessionHelper.containsAuthenticationData(httpRequest)) {
                sessionHelper.processAuthenticationData(httpRequest, currentUri, fullUrl);
                // 判断是否需要刷新accessToken
                if (sessionHelper.isAuthDataExpired(httpRequest)) {
                    sessionHelper.updateAuthDataUsingRefreshToken(httpRequest);
                }
                return true;
            }

            log.info("【User need Login , redirect to login page】");
            // not authenticated
            sessionHelper.sendAuthRedirect(httpRequest, httpResponse);

            return false;

        } catch (AuthenticationException authException) {
            log.error("【AccessInterceptor PreHandle get AuthenticationException】", authException);
            sessionHelper.removePrincipalFromSession(httpRequest);
            return false;
        } catch (Throwable e) {
            log.error("【AccessInterceptor PreHandle get error】", e);
            throw new RuntimeException(e);
        }

//        return true;


//        log.info("【AccessInterceptor begins work.  --> preHandle】 request param : " + new JSONObject(httpRequest.getParameterMap()));
//        try {
//            String currentUri = httpRequest.getRequestURL().toString();
//            String queryStr = httpRequest.getQueryString();
//            String fullUrl = currentUri + (queryStr != null ? "?" + queryStr : "");
//            currentUri = currentUri.replaceAll("http:", "https:");
//
//            // check if user has a AuthData in the session
//            if (!AuthHelper.isAuthenticated(httpRequest)) {
//                log.info("【Session has no AuthData】");
//                if (AuthHelper.containsAuthenticationData(httpRequest)) {
//                    log.info("【Request Include AuthData】");
//                    processAuthenticationData(httpRequest, currentUri, fullUrl);
//                } else {
//                    log.info("【Redirect to login page】");
//                    // not authenticated
//                    sendAuthRedirect(httpRequest, httpResponse);
//                    return false;
//                }
//            }
//            if (isAuthDataExpired(httpRequest)) {
//                log.info("【Update AuthData By Refresh Token】");
//                this.updateAuthDataUsingRefreshToken(httpRequest);
//            }
//        } catch (AuthenticationException authException) {
//            log.error("【出错了】", authException);
//            // something went wrong (like expiration or revocation of token)
//            // we should invalidate AuthData stored in session and redirect to Authorization server
//            removePrincipalFromSession(httpRequest);
//            // sendAuthRedirect(httpRequest, httpResponse);
//            return false;
//        } catch (Throwable exc) {
//            throw new RuntimeException(exc);
//        }
//        log.info("【Return Ture】");
//        return true;
    }

//    private boolean isAuthDataExpired(HttpServletRequest httpRequest) {
//        log.info("【判断AccessToken是否过期】");
//        AuthenticationResult authData = AuthHelper.getAuthSessionObject(httpRequest);
//        return authData.getExpiresOnDate().before(new Date()) ? true : false;
//    }
//
//    private void updateAuthDataUsingRefreshToken(HttpServletRequest httpRequest) throws Throwable {
//        AuthenticationResult authData =
//                getAccessTokenFromRefreshToken(AuthHelper.getAuthSessionObject(httpRequest).getRefreshToken());
//        this.setSessionPrincipal(httpRequest, authData);
//    }
//
//    private void processAuthenticationData(HttpServletRequest httpRequest, String currentUri, String fullUrl)
//            throws Throwable {
//        log.info("【从request中拿出登录信息】");
//        HashMap<String, String> params = new HashMap<>();
//        for (String key : httpRequest.getParameterMap().keySet()) {
//            params.put(key, httpRequest.getParameterMap().get(key)[0]);
//        }
//        // validate that state in response equals to state in request
//        StateData stateData = validateState(httpRequest.getSession(), params.get(STATE));
//
//        AuthenticationResponse authResponse = AuthenticationResponseParser.parse(new URI(fullUrl), params);
//        if (AuthHelper.isAuthenticationSuccessful(authResponse)) {
//            log.info("【授权成功，开始解析授权信息】");
//            AuthenticationSuccessResponse oidcResponse = (AuthenticationSuccessResponse) authResponse;
//            // validate that OIDC Auth Response matches Code Flow (contains only requested artifacts)
//            validateAuthRespMatchesCodeFlow(oidcResponse);
//
//            AuthenticationResult authData = this.getAccessToken(oidcResponse.getAuthorizationCode(), currentUri);
//            // validate nonce to prevent reply attacks (code maybe substituted to one with broader access)
//            validateNonce(stateData, getClaimValueFromIdToken(authData.getIdToken(), "nonce"));
//            // 将accessToken信息保存到session中
//            this.setSessionPrincipal(httpRequest, authData);
//        } else {
//            AuthenticationErrorResponse oidcResponse = (AuthenticationErrorResponse) authResponse;
//            throw new Exception(String.format("Request for auth code failed: %s - %s",
//                    oidcResponse.getErrorObject().getCode(),
//                    oidcResponse.getErrorObject().getDescription()));
//        }
//    }
//
//    private void validateNonce(StateData stateData, String nonce) throws Exception {
//        log.info("【validate Nonce】");
//        if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
//            throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "could not validate nonce");
//        }
//    }
//
//    private String getClaimValueFromIdToken(String idToken, String claimKey) throws ParseException {
//        return (String) JWTParser.parse(idToken).getJWTClaimsSet().getClaim(claimKey);
//    }
//
//    private void sendAuthRedirect(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
//        httpResponse.setStatus(302);
//
//        // use state parameter to validate response from Authorization server
//        String state = UUID.randomUUID().toString();
//
//        // use nonce parameter to validate idToken
//        String nonce = UUID.randomUUID().toString();
//
//        storeStateInSession(httpRequest.getSession(), state, nonce);
//
//
////        String currentUri = httpRequest.getRequestURL().toString();
//        StringBuffer url = httpRequest.getRequestURL();
//        String currentUri = url.delete(url.length() - httpRequest.getRequestURI().length(), url.length()).append(httpRequest.getSession().getServletContext().getContextPath()).append("/").toString();
//        currentUri = currentUri.replaceAll("http:", "https:") + httpRequest.getServletPath() + "?" + httpRequest.getQueryString();
//        log.info("【User Request Uri = " + currentUri + "】");
//
//        httpRequest.getSession().setAttribute(AuthParameterNames.GOTO_PATH, currentUri);
//
//        httpResponse.sendRedirect(getRedirectUrl(REPLY_PATH, state, nonce));
//    }
//
//    /**
//     * make sure that state is stored in the session,
//     * delete it from session - should be used only once
//     *
//     * @param session
//     * @param state
//     * @throws Exception
//     */
//    private StateData validateState(HttpSession session, String state) throws Exception {
//        if (StringUtils.isNotEmpty(state)) {
//            StateData stateDataInSession = removeStateFromSession(session, state);
//            if (stateDataInSession != null) {
//                return stateDataInSession;
//            }
//        }
//        throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "could not validate state");
//    }
//
//    private void validateAuthRespMatchesCodeFlow(AuthenticationSuccessResponse oidcResponse) throws Exception {
//        if (oidcResponse.getIDToken() != null || oidcResponse.getAccessToken() != null ||
//                oidcResponse.getAuthorizationCode() == null) {
//            throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "unexpected set of artifacts received");
//        }
//    }
//
//    @SuppressWarnings("unchecked")
//    private StateData removeStateFromSession(HttpSession session, String state) {
//        Map<String, StateData> states = (Map<String, StateData>) session.getAttribute(STATES);
//        if (states != null) {
//            eliminateExpiredStates(states);
//            StateData stateData = states.get(state);
//            if (stateData != null) {
//                states.remove(state);
//                return stateData;
//            }
//        }
//        return null;
//    }
//
//    @SuppressWarnings("unchecked")
//    private void storeStateInSession(HttpSession session, String state, String nonce) {
//        if (session.getAttribute(STATES) == null) {
//            session.setAttribute(STATES, new HashMap<String, StateData>());
//        }
//        ((Map<String, StateData>) session.getAttribute(STATES)).put(state, new StateData(nonce, new Date()));
//    }
//
//    private void eliminateExpiredStates(Map<String, StateData> map) {
//        Iterator<Map.Entry<String, StateData>> it = map.entrySet().iterator();
//
//        Date currTime = new Date();
//        while (it.hasNext()) {
//            Map.Entry<String, StateData> entry = it.next();
//            long diffInSeconds = TimeUnit.MILLISECONDS.
//                    toSeconds(currTime.getTime() - entry.getValue().getExpirationDate().getTime());
//
//            if (diffInSeconds > STATE_TTL) {
//                it.remove();
//            }
//        }
//    }
//
//    private AuthenticationResult getAccessTokenFromRefreshToken(
//            String refreshToken) throws Throwable {
//        AuthenticationContext context;
//        AuthenticationResult result = null;
//        ExecutorService service = null;
//        try {
//            service = Executors.newFixedThreadPool(1);
//            context = new AuthenticationContext(authority + tenant + "/", true,
//                    service);
//            Future<AuthenticationResult> future = context
//                    .acquireTokenByRefreshToken(refreshToken, new ClientCredential(CLIENT_ID, SECRET_KEY), null, null);
//            result = future.get();
//        } catch (ExecutionException e) {
//            throw e.getCause();
//        } finally {
//            service.shutdown();
//        }
//
//        if (result == null) {
//            throw new ServiceUnavailableException("authentication result was null");
//        }
//        return result;
//    }
//
//    private AuthenticationResult getAccessToken(
//            AuthorizationCode authorizationCode, String currentUri)
//            throws Throwable {
//        String authCode = authorizationCode.getValue();
//        ClientCredential credential = new ClientCredential(CLIENT_ID, SECRET_KEY);
//        AuthenticationContext context;
//        AuthenticationResult result = null;
//        ExecutorService service = null;
//        try {
//            service = Executors.newFixedThreadPool(1);
//            context = new AuthenticationContext(authority + tenant + "/", true,
//                    service);
//            log.info("【Get AccessToken by authCode】");
//            Future<AuthenticationResult> future = context
//                    .acquireTokenByAuthorizationCode(authCode, new URI(
//                            currentUri), credential, null);
//            result = future.get();
//        } catch (ExecutionException e) {
//            throw e.getCause();
//        } finally {
//            service.shutdown();
//        }
//
//        if (result == null) {
//            throw new ServiceUnavailableException("authentication result was null");
//        }
//        return result;
//    }
//
////    private void setSessionPrincipal(HttpServletRequest httpRequest,
////                                     AuthenticationResult result) {
////        httpRequest.getSession().setAttribute(AuthHelper.PRINCIPAL_SESSION_NAME, result);
////    }
//
//    private void removePrincipalFromSession(HttpServletRequest httpRequest) {
//        log.info("【Remove Principal From Session】");
//        httpRequest.getSession().removeAttribute(AuthHelper.PRINCIPAL_SESSION_NAME);
//    }
//
//    private String getRedirectUrl(String currentUri, String state, String nonce)
//            throws UnsupportedEncodingException {
//        String redirectUrl = authority
//                + this.tenant
//                + "/oauth2/authorize?response_type=code&scope=directory.read.all&response_mode=form_post&redirect_uri="
//                + URLEncoder.encode(currentUri, "UTF-8") + "&client_id="
//                + CLIENT_ID + "&resource=https%3a%2f%2fgraph.microsoft.com"
//                + "&state=" + state
//                + "&nonce=" + nonce;
//        log.info("【Redirect URL】URL = " + redirectUrl);
//        return redirectUrl;
//    }


    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
